Assignment 3 Write-up
Name: Ge Jin
Student ID: 3037754475
Link: https://cal-cs184-student.github.io/hw-webpages-sp24-maxwelljin/hw3/index.html
Overview
In this project, we are rendering a realistic scenario, which comprises five different parts: ray generation and scene intersection, bounding volume hierarchy, direct illumination and global illumination, and adaptive sampling. In ray generation, we randomly cast rays from the camera, and we need to determine if these rays intersect with any objects, which requires developing efficient intersection algorithms for quick verification. Regarding the bounding volume hierarchy, it acts as an acceleration structure to speed up the process, as iterating through all possible object intersections would be impractical. For direct and global illumination, we employ the rendering equation to calculate the incoming light and use the BRDF (Bidirectional Reflectance Distribution Function) to determine how much light is reflected back to the camera. Finally, given the complexity of the lighting setup, we focus on sampling the parts with the most variance and strive to make the image rendering converge faster.
Part 1
In the ray generation algorithm, we start by casting a ray from the camera's position through each pixel on the image plane. This process involves determining the direction of each ray so that it passes through the center of the corresponding pixel and extends into the scene. The direction is usually calculated based on the camera's orientation, field of view, and the position of the pixel within the image plane. This method ensures that the ray accurately represents the path that light would take to reach that specific point on the camera's sensor.
To test the triangle intersection, we use the barycentric coordinate system, which helps determine whether the intersection point of the ray with the triangle's plane lies within the triangle's boundaries. The barycentric coordinates, alpha, beta, and gamma, represent the weights or influences of the triangle's vertices on the specific point within the triangle. For sphere, we utilize the formula present in the course.
(c) Sample Image:

Part 2
(a) The BVH construction algorithm I use aims to evenly split the volume until it contains the maximum number of elements. The primary strategy involves using the mean value of the geometry bounds as the threshold for splitting. To determine the axis along which to split, I calculate the variance for each axis (X, Y, and Z) and select the one with the largest variance for splitting. This method aims to balance the number of primitives in each subtree, leading to a more efficient traversal during rendering.
(b)

(c) In practice, moderately complex geometry, such as a cube, benefits significantly from BVH acceleration. For instance, on my M2 computer, rendering with BVH acceleration takes only 1-2 seconds, whereas without BVH, it requires at least 50 seconds. The efficiency of BVH comes from its logarithmic time complexity (log(N)), which substantially reduces the number of intersection tests needed during ray tracing, especially in scenes with a large number of primitives.
Part 3
(a) In the direct lighting function, we implement the BRDF function for diffuse materials, which reflects light equally in all directions. The process is straightforward: we use a sampler to cast rays in various directions to measure the amount of light reflected at the sample point. Generally, casting more light leads to better results. Moreover, the BRDF represents the relationship between incoming and outgoing light, based on the material's properties. Integrating the BRDF into the rendering equation allows us to compute the direct lighting.
(b)

Above: Hemisphere sampling
Above: Importance sampling
(c)

Images: 1, 4, 16, 64 lights (the top is 1 light)
(d) Comparing uniform hemisphere sampling and importance lighting sampling reveals that the latter yields significantly better results. Importance lighting sampling casts light primarily in the direction where light is heading, which enhances our ability to track point light sources. Uniform hemisphere sampling, on the other hand, is prone to noise because it casts rays into areas without light, creating noise that affects image quality.
Part 4
(a) In the indirect lighting function, I use the rendering equation to simulate higher-order lighting. The rendering equation shows that the output light at a point p in direction w0 is the sum of the emission from p in w0 and the reflected light.
Specifically, I've modified the at_least_one_bounce_radiance function. This function takes a ray and an intersection point as inputs. We first estimate the direct lighting at this intersection point through the importance lighting function we just implemented. Then, to calculate the outgoing light due to reflection, we need to sample the reflected light using Monte Carlo integration and multiply it by the BRDF. To sample the reflected light, we cast a ray in a chosen direction, and if it intersects with an object, we recursively call at_least_one_bounce_radiance for the new sample ray, multiplying by the BSDF value and the cosine of the angle between the normal and the direction of the reflected light, and dividing by the PDF to account for importance sampling.
To avoid infinite recursion, we terminate the recursive call when we reach the maximum depth. However, this can introduce bias into our sampling process. Therefore, we use the Russian Roulette rendering approach, where we randomly terminate the recursive process to balance between efficiency and accuracy.
(b) Sample rendering:
(c)

Only Direct
Only Indirect
(d)

Images: From max_ray_depth 0-5 (level 0 is on the top)
In the 2nd bounce of light, I observed the light reflecting from the ground onto the bunny, which resulted in the bottom part of the bunny appearing illuminated and significantly brighter than in the original images. This illumination enhancement is particularly notable, as it indicates the indirect lighting effect. Additionally, there is a distinct light presence on the top wall, which suggests that the light is not only reflecting from the ground but also bouncing off the other three walls, creating a more complex and realistic light within the scene.
In the 3rd bounce of light, the scene becomes much darker Since there’s substantial reduction in direct light reaching the bunny, the bunny appears almost entirely black, with only faint illumination due to light diffusion. The light intensity during this bounce is considerably smaller compared to the 2nd bounce, indicating a rapid decrease in light energy as it continues to bounce within the environment. Most of the residual light is scattered onto the walls, further diminishing the direct illumination on the bunny and contributing to the overall dimming of the scene.
(e)

Images: From max_ray_depth 0-5 (level 0 is on the top)
As we increase the max_ray_depth, the lighting in the shadows becomes significantly more realistic, and the brightness of the image more closely resembles real-world conditions. Additionally, increasing from one bounce to two bounces successfully renders the bottom lighting of the bunny.
(f)

Images: From max_ray_depth 0-100 (level 0 is on the top) with Russian Roulette
(g)

Images: Sample rate from 1-1024 (1 is on the top)
When we increase the sample-per-pixel rate, it can significantly reduce the noise in the image. This improvement occurs because if we cast a light from the camera angle and that light does not hit any light source (even though we use importance sampling to mitigate this issue), it will create black dots in the image. By sampling more per pixel, we can achieve higher quality and more realistic images.
Part 5
(a)
In adaptive sampling, our goal is to sample more in areas where noise is more frequent, or in other words, where the lighting conditions are more complex. The algorithm is straightforward: we trace the samples we've already collected and calculate their variance and mean. We define the variable I as 1.96 * sigma / sqrt(mean) and compare it to our maxTolerance times the average values.
In the implementation, we sample the pixels in batches. This means we calculate the initial mean and variance in the first batch and check if the variance converges to a point where the output is stable. If it's not stable, we continue sampling until we exhaust all available maximum samples. By utilizing this approach, we can set the sample number to a relatively large number. For example, in rendering images for this section, we use 2048 samples instead of 1024 because, in most parts, it won't really utilize all available samples, allowing the algorithm to focus only on the more important areas.
(b)

In the images, we can see the algorithm heavily samples in areas like the bottom of the bunny and its shadows. Since the 3D shape of the bunny is irregular and complex, the light may reflect multiple times, requiring more samples to converge. Similarly, in scenarios with the bunny and sphere, we observe heavy sampling in the shadows and at the base on the top, indicating these are areas of complex lighting that benefit from additional sampling.